import { AgentFlow, FlowNode, Connection } from '../types/agent/types';
import { customNodeManager } from '../components/AgentBuilder/NodeCreator/CustomNodeManager';

export class FlowCodeGenerator {
  private flow: AgentFlow;
  private nodes: FlowNode[];
  private connections: Connection[];
  private customNodeTypes: Set<string>;

  constructor(
    flow: AgentFlow, 
    nodes: FlowNode[], 
    connections: Connection[], 
    customNodeTypes: Set<string>
  ) {
    this.flow = flow;
    this.nodes = nodes;
    this.connections = connections;
    this.customNodeTypes = customNodeTypes;
  }

  generateCode(): string {
    const className = this.generateClassName();
    const customNodesCode = this.generateCustomNodesCode();
    const flowDataCode = this.generateFlowDataCode();
    const executionMethodCode = this.generateExecutionMethodCode();
    
    return `/**
 * Generated by Clara Agent Studio
 * Flow: ${this.flow.name}
 * Description: ${this.flow.description || 'No description provided'}
 * Generated at: ${new Date().toISOString()}
 */

import { ClaraFlowRunner } from 'clara-flow-sdk';

export class ${className} {
  private runner;
  private flowData;

  constructor(options = {}) {
    this.runner = new ClaraFlowRunner({
      enableLogging: true,
      logLevel: 'info',
      ...options
    });
    
    this.flowData = ${flowDataCode};
    
    // Register custom nodes
    this.registerCustomNodes();
  }

${customNodesCode}

  async registerCustomNodes() {
    ${this.generateCustomNodeRegistration()}
  }

${executionMethodCode}

  // Utility methods
  validateFlow() {
    return this.runner.validateFlow(this.flowData);
  }

  getExecutionLogs() {
    return this.runner.getExecutionLogs();
  }

  clearLogs() {
    this.runner.clearLogs();
  }

  getStats() {
    return this.runner.getStats();
  }

  dispose() {
    this.runner.dispose();
  }
}

// Export instance for direct use
export const ${this.generateInstanceName()} = new ${className}();

// Export default for easier importing
export default ${className};

/**
 * Usage Examples:
 * 
 * // Basic usage with default instance
 * import { ${this.generateInstanceName()} } from './${this.generateFileName()}';
 * 
 * const result = await ${this.generateInstanceName()}.execute({
 *   // your input data here
 * });
 * console.log(result);
 * 
 * // Custom instance with options
 * import ${className} from './${this.generateFileName()}';
 * 
 * const flow = new ${className}({
 *   enableLogging: false,
 *   timeout: 30000
 * });
 * 
 * const result = await flow.execute({ message: "Hello World" });
 * 
 * // Batch processing
 * const inputs = [
 *   { message: "First input" },
 *   { message: "Second input" }
 * ];
 * 
 * const results = await flow.executeBatch(inputs);
 * 
 * // Stream processing
 * await flow.executeWithCallback(
 *   { message: "Stream input" },
 *   (nodeId, result) => {
 *     console.log(\`Node \${nodeId} completed:\`, result);
 *   }
 * );
 */`;
  }

  private generateClassName(): string {
    return this.flow.name
      .replace(/[^a-zA-Z0-9]/g, '_')
      .replace(/^_+|_+$/g, '')
      .replace(/_+/g, '_')
      .split('_')
      .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
      .join('') + 'Flow';
  }

  private generateInstanceName(): string {
    return this.flow.name
      .replace(/[^a-zA-Z0-9]/g, '_')
      .replace(/^_+|_+$/g, '')
      .replace(/_+/g, '_')
      .toLowerCase() + 'Flow';
  }

  private generateFileName(): string {
    return this.flow.name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase() + '_flow';
  }

  private generateFlowDataCode(): string {
    const flowData = {
      format: 'clara-sdk',
      version: '1.0.0',
      flow: {
        id: this.flow.id,
        name: this.flow.name,
        description: this.flow.description,
        nodes: this.nodes.map(node => this.cleanNodeData(node)),
        connections: this.connections.map(conn => ({
          id: conn.id,
          sourceNodeId: conn.sourceNodeId,
          sourcePortId: conn.sourcePortId,
          targetNodeId: conn.targetNodeId,
          targetPortId: conn.targetPortId
        })),
        metadata: {
          createdAt: this.flow.createdAt,
          updatedAt: this.flow.updatedAt
        }
      },
      customNodes: this.generateCustomNodeDefinitions(),
      exportedAt: new Date().toISOString(),
      exportedBy: 'Clara Agent Studio'
    };

    return JSON.stringify(flowData, null, 4);
  }

  private generateCustomNodesCode(): string {
    if (this.customNodeTypes.size === 0) {
      return '';
    }

    const customNodeMethods = Array.from(this.customNodeTypes).map(nodeType => {
      const customNode = customNodeManager.getCustomNode(nodeType);
      if (!customNode) return '';

      return `
  // Custom node: ${customNode.name}
  register${this.capitalizeFirst(nodeType.replace(/[^a-zA-Z0-9]/g, ''))}Node() {
    const nodeDefinition = {
      id: '${customNode.id}',
      type: '${customNode.type}',
      name: '${customNode.name}',
      description: '${customNode.description}',
      category: '${customNode.category}',
      icon: '${customNode.icon}',
      inputs: ${JSON.stringify(customNode.inputs, null, 6)},
      outputs: ${JSON.stringify(customNode.outputs, null, 6)},
      properties: ${JSON.stringify(customNode.properties, null, 6)},
      executionCode: \`${customNode.executionCode}\`,
      metadata: ${JSON.stringify(customNode.metadata || {}, null, 6)}
    };

    return this.runner.registerCustomNode(nodeDefinition);
  }`;
    }).filter(Boolean).join('\n');

    return customNodeMethods;
  }

  private generateCustomNodeRegistration(): string {
    if (this.customNodeTypes.size === 0) {
      return '// No custom nodes to register';
    }

    const registrations = Array.from(this.customNodeTypes).map(nodeType => {
      const methodName = this.capitalizeFirst(nodeType.replace(/[^a-zA-Z0-9]/g, ''));
      return `    await this.register${methodName}Node();`;
    }).join('\n');

    return registrations;
  }

  private generateExecutionMethodCode(): string {
    const inputNodes = this.nodes.filter(node => node.type === 'input');
    const outputNodes = this.nodes.filter(node => node.type === 'output');

    const inputParams = inputNodes.map(node => {
      const label = node.data?.label || 'input';
      const paramName = label.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
      return `${paramName}`;
    });

    const inputMapping = inputNodes.map(node => {
      const label = node.data?.label || 'input';
      const paramName = label.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
      return `      '${node.id}': inputs.${paramName} !== undefined ? inputs.${paramName} : inputs['${label}']`;
    });

    return `
  /**
   * Execute the flow with the provided inputs
   * @param {Object} inputs - Input data for the flow
   * @returns {Promise<Object>} - Flow execution results
   */
  async execute(inputs = {}) {
    // Map inputs to flow nodes
    const flowInputs = {
${inputMapping.join(',\n')}
    };

    // Execute the flow
    const result = await this.runner.executeFlow(this.flowData, flowInputs);
    
    return result;
  }

  /**
   * Execute the flow with a callback for each node completion
   * @param {Object} inputs - Input data for the flow
   * @param {Function} onNodeComplete - Callback function called when each node completes
   * @returns {Promise<Object>} - Flow execution results
   */
  async executeWithCallback(inputs = {}, onNodeComplete) {
    const flowInputs = {
${inputMapping.join(',\n')}
    };

    // Setup callback monitoring
    const originalExecuteNode = this.runner.executeNode;
    this.runner.executeNode = async (nodeData, nodeInputs) => {
      const result = await originalExecuteNode.call(this.runner, nodeData, nodeInputs);
      if (onNodeComplete) {
        onNodeComplete(nodeData.id, result);
      }
      return result;
    };

    try {
      const result = await this.runner.executeFlow(this.flowData, flowInputs);
      return result;
    } finally {
      // Restore original method
      this.runner.executeNode = originalExecuteNode;
    }
  }

  /**
   * Execute the flow with multiple input sets (batch processing)
   * @param {Array<Object>} inputSets - Array of input data objects
   * @param {Object} options - Batch processing options
   * @returns {Promise<Array<Object>>} - Array of flow execution results
   */
  async executeBatch(inputSets, options = {}) {
    const { 
      concurrency = 1, 
      continueOnError = false,
      progressCallback = null 
    } = options;

    const results = [];
    const errors = [];
    
    if (concurrency === 1) {
      // Sequential processing
      for (let i = 0; i < inputSets.length; i++) {
        try {
          const result = await this.execute(inputSets[i]);
          results.push({ index: i, success: true, result });
          if (progressCallback) {
            progressCallback(i + 1, inputSets.length, result);
          }
        } catch (error) {
          const errorResult = { index: i, success: false, error: error.message };
          errors.push(errorResult);
          results.push(errorResult);
          
          if (!continueOnError) {
            throw new Error(\`Batch processing failed at index \${i}: \${error.message}\`);
          }
          
          if (progressCallback) {
            progressCallback(i + 1, inputSets.length, null, error);
          }
        }
      }
    } else {
      // Parallel processing with concurrency limit
      const chunks = [];
      for (let i = 0; i < inputSets.length; i += concurrency) {
        chunks.push(inputSets.slice(i, i + concurrency));
      }

      let processedCount = 0;
      for (const chunk of chunks) {
        const chunkPromises = chunk.map(async (inputs, chunkIndex) => {
          const globalIndex = processedCount + chunkIndex;
          try {
            const result = await this.execute(inputs);
            return { index: globalIndex, success: true, result };
          } catch (error) {
            return { index: globalIndex, success: false, error: error.message };
          }
        });

        const chunkResults = await Promise.all(chunkPromises);
        results.push(...chunkResults);
        
        processedCount += chunk.length;
        
        if (progressCallback) {
          chunkResults.forEach((result, index) => {
            if (result.success) {
              progressCallback(result.index + 1, inputSets.length, result.result);
            } else {
              progressCallback(result.index + 1, inputSets.length, null, new Error(result.error));
            }
          });
        }

        // Check for errors if not continuing on error
        if (!continueOnError) {
          const chunkErrors = chunkResults.filter(r => !r.success);
          if (chunkErrors.length > 0) {
            throw new Error(\`Batch processing failed at indices: \${chunkErrors.map(e => e.index).join(', ')}\`);
          }
        }
      }
    }

    return results;
  }`;
  }

  private generateCustomNodeDefinitions(): any[] {
    const definitions: any[] = [];
    
    for (const nodeType of this.customNodeTypes) {
      const customNode = customNodeManager.getCustomNode(nodeType);
      if (customNode) {
        definitions.push({
          id: customNode.id,
          type: customNode.type,
          name: customNode.name,
          description: customNode.description,
          category: customNode.category,
          icon: customNode.icon,
          inputs: customNode.inputs,
          outputs: customNode.outputs,
          properties: customNode.properties,
          executionCode: customNode.executionCode,
          metadata: customNode.metadata
        });
      }
    }
    
    return definitions;
  }

  private cleanNodeData(node: FlowNode): any {
    return {
      id: node.id,
      type: node.type,
      data: node.data,
      position: node.position
    };
  }

  private capitalizeFirst(str: string): string {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }
} 